#include "emu.h"
#include "device.h"
#include "sdcard.h"
#include <getopt.h>
#include <signal.h>
#include <unistd.h>
#include "ram.h"
#include "zlib.h"
#include "compress.h"
#include <list>
// #include "remote_bitbang.h"

// extern remote_bitbang_t * jtag;

inline EmuArgs parse_args(int argc, const char *argv[]) {
  EmuArgs args;
  int o;
  while ( (o = getopt(argc, const_cast<char *const*>(argv),"i:d:w")) != -1) {
    switch (o) {
      case 'w': args.enable_waveform = true;break;
      case 'i': args.image = optarg; break;
      case 'd': args.sd_image = optarg;break;
      default:
        printf("default, result=%c\n",o);
        break;
    }
  }

  Verilated::commandArgs(argc, argv); // Prepare extra args for TLMonitor
  return args;
}

Emulator::Emulator(int argc, const char *argv[]):dut_ptr(new VRena),cycles(0)
{
  args = parse_args(argc, argv);
  // srand
  srand(args.seed);
  srand48(args.seed);
  Verilated::randReset(2);
  assert_init();
  // // init remote-bitbang
  // if (args.enable_jtag) { 
  //   jtag = new remote_bitbang_t(23334);
  // }
  // init core
  reset_ncycles(10);
  // init ram
  init_ram(args.image);
  init_device(args.sd_image);

#if VM_TRACE == 1
  enable_waveform = args.enable_waveform;
  if (enable_waveform ) {
    Verilated::traceEverOn(true);	// Verilator must compute traced signals
    tfp = new VerilatedVcdC;
    dut_ptr->trace(tfp, 99);	// Trace 99 levels of hierarchy
    if (args.wave_path != NULL) {
      tfp->open(args.wave_path);
    }
    else {
      time_t now = time(NULL);
      tfp->open(waveform_filename(now));	// Open the dump file
    }
  }
#endif
}

Emulator::~Emulator() {
  ram_finish();
  assert_finish();
}

inline void Emulator::reset_ncycles(size_t cycles) {
  for(int i = 0; i < cycles; i++) {
    dut_ptr->reset = 1;
    dut_ptr->clock = 0;
    dut_ptr->eval();
    dut_ptr->clock = 1;
    dut_ptr->eval();
    dut_ptr->reset = 0;
  }
}

uint64_t Emulator::execute() {

  uint32_t lasttime_poll = 0;
  uint32_t t = uptime();
  if (t - lasttime_poll > 100) {
    poll_event();
    lasttime_poll = t;
  }

  while (!Verilated::gotFinish()) {
    dut_ptr->clock = 0;
    dut_ptr->eval();

  #if VM_TRACE == 1
    if (enable_waveform) {
      // uint64_t cycle = trap->cycleCnt;
      tfp->dump(cycles);
    }
  #endif

    dut_ptr->clock = 1;
    dut_ptr->eval();

    // if (dut_ptr->io_uart_out_valid) {
    //   printf("%c", dut_ptr->io_uart_out_ch);
    //   fflush(stdout);
    // }
    // if (dut_ptr->io_uart_in_valid) {
    //   extern uint8_t uart_getc();
    //   dut_ptr->io_uart_in_ch = uart_getc();
    // }
    cycles ++;
  }

#if VM_TRACE == 1
  if (enable_waveform) tfp->close();
#endif

  return 0;
}

inline char* Emulator::timestamp_filename(time_t t, char *buf) {
  char buf_time[64];
  strftime(buf_time, sizeof(buf_time), "%F@%T", localtime(&t));
  char *noop_home = getenv("NOOP_HOME");
  assert(noop_home != NULL);
  int len = snprintf(buf, 1024, "%s/build/%s", noop_home, buf_time);
  return buf + len;
}

inline char* Emulator::waveform_filename(time_t t) {
  static char buf[1024];
  char *p = timestamp_filename(t, buf);
  strcpy(p, ".vcd");
  eprintf(ANSI_COLOR_BLUE "dump wave to %s...\n" ANSI_COLOR_RESET, buf); 
  return buf;
}

inline char* Emulator::cycle_wavefile(uint64_t cycles, time_t t) {
  static char buf[1024];
  char buf_time[64];
  strftime(buf_time, sizeof(buf_time), "%F@%T", localtime(&t));
  char *noop_home = getenv("NOOP_HOME");
  assert(noop_home != NULL);
  int len = snprintf(buf, 1024, "%s/build/%s_%ld", noop_home, buf_time, cycles);
  strcpy(buf + len, ".vcd");
  printf("dump wave to %s...\n", buf);
  return buf;
}